package tresp.bthreads;

import java.awt.geom.Point2D;
import java.util.concurrent.atomic.AtomicReference;

import robocode.Rules;
import robocode.ScannedRobotEvent;
import tresp.coordinator.ICoordinator;
import tresp.utils.BThreadVote;
import tresp.utils.ITankInfo;

/**
 * <b>PredictiveTargeting</b>
 * 
 * 
 * @author <font color="blue">Maxim Kirilov</font>
 *
 */
public class PredictiveTargeting extends BThread {

//    private static Logger logger = MyLogger.getLogger( PredictiveTargeting.class );
    
    private ITankInfo tankInfo;
    
    private AtomicReference<ScannedRobotEvent> lastScannedRobotEvent;
    
    
    public PredictiveTargeting(ICoordinator<BThreadVote> coordinator) {
	super(coordinator);
	tankInfo = coordinator.getTankInfo();
	lastScannedRobotEvent = new AtomicReference<ScannedRobotEvent>();
    }

    @Override
    public void vote() {
	
	ScannedRobotEvent enemy = lastScannedRobotEvent.get();
	if ( enemy != null && !tankInfo.isTeammate( enemy.getName() )){
	    
	    double firePower = Math.min(500 / enemy.getDistance(), 3);
	    double bulletSpeed =  Rules.getBulletSpeed( firePower );;
	    
	    long time = (long)(enemy.getDistance() / bulletSpeed);
	    
	    double absBearingDeg = tankInfo.getHeading() + enemy.getBearing();
	    
	    if (absBearingDeg < 0.0D) {
		absBearingDeg += 360.0D;
	    }
	    
	    double absBearingRadians =  Math.toRadians( absBearingDeg );
	    
	    double x = (tankInfo.getX() + Math.sin( absBearingRadians ) * enemy.getDistance());
	    double y = (tankInfo.getY() + Math.cos( absBearingRadians ) * enemy.getDistance());
	    
	    
	    double futureX = x + Math.sin( enemy.getHeadingRadians() ) * enemy.getVelocity() * time;
	    double futureY = y + Math.cos( enemy.getHeadingRadians() ) * enemy.getVelocity() * time; 
	    
	    double absDeg = absoluteBearing(tankInfo.getX(), tankInfo.getY(), futureX, futureY);
	    
	    double turnDeg = normalizeBearing(absDeg - tankInfo.getGunHeading());
	    
	    coordinator.vote(createBThreadVote(BThreadType.TURN_GUN_RIGHT_DEGREES, weight, turnDeg));
	    
	    if ((Math.abs( tankInfo.getGunTurnRemaining() ) < 10) && (tankInfo.getGunHeat() == 0)){
		coordinator.vote(createBThreadVote(BThreadType.FIRE, weight, firePower));
	    }
	    
	}else{  
	    coordinator.vote( createBThreadVote(BThreadType.TURN_RADAR_LEFT_DEGREES, weight, 45D) );
	}

    }

    @Override
    public void onScannedRobot(ScannedRobotEvent event){
	super.onScannedRobot(event);
	lastScannedRobotEvent.set(event);
    }
    
    
    
    @Override
    public String getName() {
	return "PredictiveTargeting";
    }

    
    /**
     * computes the absolute bearing between two points
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @return
     */
    double absoluteBearing(double x1, double y1, double x2, double y2) {
	double xo = x2-x1;
	double yo = y2-y1;
	double hyp = Point2D.distance(x1, y1, x2, y2);
	double arcSin = Math.toDegrees(Math.asin(xo / hyp));
	double bearing = 0;

	if (xo > 0 && yo > 0) { // both pos: lower-Left
	    bearing = arcSin;
	} else if (xo < 0 && yo > 0) { // x neg, y pos: lower-right
	    bearing = 360 + arcSin; // arcsin is negative here, actuall 360 - ang
	} else if (xo > 0 && yo < 0) { // x pos, y neg: upper-left
	    bearing = 180 - arcSin;
	} else if (xo < 0 && yo < 0) { // both neg: upper-right
	    bearing = 180 - arcSin; // arcsin is negative here, actually 180 + ang
	}

	return bearing;
    }
    
    
    /**
     * normalizes a bearing to between +180 and -180 
     * @param angle
     * @return
     */
    double normalizeBearing(double angle) {
	while (angle >  180) angle -= 360;
	while (angle < -180) angle += 360;
	return angle;
    }
    
}
